package com.idealighter.game.games.errenniuniu.manager;

import com.alibaba.fastjson.JSONObject;

import com.google.common.collect.Lists;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.idealighter.game.chat.manager.ChatMsgMgr;
import com.idealighter.game.common.CommonMsgMgr;
import com.idealighter.game.core.common.Game;
import com.idealighter.game.core.constant.BoolInteger;
import com.idealighter.game.core.constant.Operator;
import com.idealighter.game.core.constant.notice.NoticeType;
import com.idealighter.game.core.error.ErrorCode;
import com.idealighter.game.dblog.service.DbLogService;
import com.idealighter.game.dictionary.dic.ErrenniuniuRoomDic;
import com.idealighter.game.dictionary.dic.ErrenniuniuTimeDic;
import com.idealighter.game.dictionary.dic.GameNoticeDic;
import com.idealighter.game.dictionary.domain.ErrenniuniuRoomDomain;
import com.idealighter.game.dictionary.domwrapper.GameNoticeDomainWrapper;
import com.idealighter.game.event.manager.EventMgr;
import com.idealighter.game.event.struct.ClearGameDataEvent;
import com.idealighter.game.event.struct.PlayerExitEvent;
import com.idealighter.game.event.struct.ShutdownRoomEvent;
import com.idealighter.game.event.struct.StartRoomEvent;
import com.idealighter.game.gamehall.event.EnterRoomEvent;
import com.idealighter.game.gamehall.event.EnterTableEvent;
import com.idealighter.game.gamehall.event.ExitRoomEvent;
import com.idealighter.game.gamehall.event.ExitTableEvent;
import com.idealighter.game.gamehall.event.RoomMemInfoUpdateEvent;
import com.idealighter.game.gamehall.manager.GameHallMgr;
import com.idealighter.game.games.common.AbstractRoom;
import com.idealighter.game.games.common.SeatState;
import com.idealighter.game.games.common.niuniu.NiuNiuCard;
import com.idealighter.game.games.common.niuniu.NiuNiuCardsType;
import com.idealighter.game.games.common.niuniu.NiuNiuCardsTypeComparator;
import com.idealighter.game.games.common.niuniu.NiuNiuCardsTypeGetter;
import com.idealighter.game.games.errenniuniu.struct.ErRenNiuNiuRoom;
import com.idealighter.game.games.errenniuniu.struct.ErRenNiuNiuSeat;
import com.idealighter.game.games.errenniuniu.struct.ErRenNiuNiuTable;
import com.idealighter.game.log.core.LogReason;
import com.idealighter.game.log.struct.game.Game2WinLoseLog;
import com.idealighter.game.log.struct.game.GameAfeeLog;
import com.idealighter.game.login.manager.LoginMgr;
import com.idealighter.game.notice.manager.NoticeMsgMgr;
import com.idealighter.game.player.manager.PlayerMgr;
import com.idealighter.game.player.service.bo.PlayerBo;
import com.idealighter.game.player.struct.Player;
import com.idealighter.game.schedule.manager.ScheduleMgr;
import com.idealighter.game.server.ExecutorMgr;
import com.idealighter.game.server.core.executor.DisruptorExecutor;
import com.idealighter.utils.code.RandCodeUtil;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 二人牛牛 .
 * 
 * @date 2015年9月15日 下午7:40:10
 *
 */
@Singleton
public class ErRenNiuNiuMgr {

  private static final Logger LOG = LoggerFactory.getLogger(ErRenNiuNiuMgr.class);

  // 二人牛牛定时服务器增加的延时，因为与网络延时等原因
  private static final int ADDED_DELAY = 5;
  // 玩家手上的牌数
  public static final int PLAYER_CARDS = 5;

  private final EventMgr eventMgr;
  @Inject
  private ErRenNiuNiuDataMgr dataMgr;
  @Inject
  private ErRenNiuNiuMsgMgr msgMgr;
  @Inject
  private ErrenniuniuRoomDic roomDic;
  @Inject
  private ErrenniuniuTimeDic timeDic;
  @Inject
  private GameNoticeDic gameNoticeDic;
  @Inject
  private PlayerMgr playerMgr;
  @Inject
  private ScheduleMgr scheduleMgr;
  @Inject
  private ErRenNiuNiuControlScript niuniuControl;
  @Inject
  private CommonMsgMgr commonMsgMgr;
  @Inject
  private GameHallMgr gameHallMgr;
  @Inject
  private NoticeMsgMgr noticeMsgMgr;
  @Inject
  private ChatMsgMgr chatMsgMgr;

  @Inject
  private LoginMgr loginMgr;
  // 游戏线程executor,可以执行普通任务和定时任务
  private final DisruptorExecutor gameExecutor;

  /**
   * 构造函数.
   * 
   * @param eventMgr .
   * @param executorMgr .
   */
  @Inject
  public ErRenNiuNiuMgr(EventMgr eventMgr, ExecutorMgr executorMgr) {
    this.eventMgr = eventMgr;
    eventMgr.register(this);
    this.gameExecutor = executorMgr.getGameExecutor(Game.ERREN_NIUNIU.getModuleId());
  }

  /**
   * 订阅玩家退出游戏事件,在gameExecutor中执行,避免线程安全问题 .
   * 
   * @param event .
   */
  @Subscribe
  public void onPlayerExitGame(PlayerExitEvent event) {
    gameExecutor.execute(() -> {
      Player player = event.player;
      long playerId = player.getId();

      ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(playerId);
      if (seat != null) {
        // 玩家在房间中但是没有准备，退出桌子，退出房间
        doExitTabl(seat);
      }

      // 玩家是否在牛牛房间中
      if (player.curRoom instanceof ErRenNiuNiuRoom) {
        // 退出房间
        exitRoom(player);
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onStartRoom(StartRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.ERREN_NIUNIU) {
        roomDic.load();
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onShutDownRoom(ShutdownRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.ERREN_NIUNIU) {
        roomDic.load();
        AbstractRoom room = dataMgr.getRoom(event.getRoomId());
        for (Long playerId : room.getPlayers()) {
          Player player = playerMgr.getPlayer(playerId);
          if (player != null) {
            loginMgr.noticeLogoutForGameExit(player);
          }
        }
      }
    });
  }

  @Subscribe
  public void onClearGameData(ClearGameDataEvent event) {
    gameExecutor.execute(() -> doClearGameData(event.playerId));
  }

  /**
   * 玩家进入游戏大厅 .
   * 
   * @param player 玩家.
   */
  public void enterGameHall(Player player) {
    if (player.curRoom != null) {
      Game game = player.curRoom.game();
      LOG.warn("[二人牛牛]玩家[{}][{}]在[{}]游戏中，无法进入游戏", player.getId(), player.getPlayerName(),
          game.getDesc());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    msgMgr.sendEnterGameHallMsg(player);
    LOG.info("[二人牛牛]玩家[{}][{}]进入大厅成功", player.getPlayerName(), player.getId());
  }

  /**
   * 玩家进入房间 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void enterRoom(Player player, int roomId) {
    AbstractRoom curRoom = player.curRoom;
    if (curRoom != null) {
      Game game = curRoom.game();
      LOG.warn("[二人牛牛]玩家[{}][{}]当前在游戏[{}]房间[{}]中不能进入房间", player.getId(), player.getPlayerName(),
          curRoom.getId());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    ErRenNiuNiuRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    ErrenniuniuRoomDomain roomDom = roomDic.map().get(roomId);
    if (roomDom.getIsActive() != 1) {
      LOG.error("[二人牛牛]玩家[{}][{}]进入的房间[{}]已经关闭", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getLower());
      return;
    }
    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getUpper());
      return;
    }

    if (!player.vip()
        && room.getPlayers().size() >= roomDom.getMaxNum() * roomDom.getOrdinarPeople() / 100) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_VIP_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的房间[{}]普通玩家人数已超过上限", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      return;
    }

    if (room.getPlayers().size() >= roomDom.getMaxNum()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的房间[{}]已超过最大人数[{}]", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      return;
    }

    // 修改玩家当前房间
    player.curRoom = room;
    room.getPlayers().add(player.getId());
    // 发送进入房间消息
    msgMgr.sendEnterRoomMsg(player);
    LOG.info("[二人牛牛]玩家[{}][{}]进入房间[{}]成功", player.getPlayerName(), player.getId(), roomId);

    // 触发进入房间事件
    eventMgr.post(new EnterRoomEvent(player, room));
  }

  /**
   * 玩家金币兑换筹码 .
   * 
   * @param player 玩家.
   * @param gold . 兑换筹码的金币
   */
  public void exchangeChips(Player player, long gold) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]不在牌桌中不能兑换筹码", player.getPlayerName(), player.getId());
      return;
    }

    if (player.getGold() < gold
        + convertGold(seat.table.room.getId().intValue(), seat.getTotalChips())) {
      LOG.error("[二人牛牛]玩家[{}][{}]兑换的金币[{}]加上筹码[{}]兑换的金币超过自己的金币总和[{}]", player.getPlayerName(),
          player.getId(), gold, seat.getTotalChips(), player.getGold());
      return;
    }

    if (seat.readied()) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经开始游戏不能兑换筹码", player.getPlayerName(), player.getId());
      return;
    }

    long chips = convertChip(seat.table.room.getId().intValue(), gold);
    seat.setTotalChips(seat.getTotalChips() + chips);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);

    LOG.info("[二人牛牛]玩家[{}][{}]用金币[{}]兑换筹码[{}]成功", player.getPlayerName(), player.getId(), gold,
        chips);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家筹码兑换金币 .
   * 
   * @param player 玩家.
   * @param chips .
   */
  public void exchangeGold(Player player, long chips) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]不在牌桌中不能兑换金币", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.getTotalChips() < chips) {
      LOG.error("[二人牛牛]玩家[{}][{}]兑换的筹码[{}]超过自己的筹码总和[{}]", player.getPlayerName(), player.getId(),
          chips, seat.getTotalChips());
      return;
    }

    if (seat.readied()) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经开始游戏不能兑换金币", player.getPlayerName(), player.getId());
      return;
    }

    seat.setTotalChips(seat.getTotalChips() - chips);
    long gold = convertGold(seat.table.room.getId().intValue(), chips);

    // 发送兑换筹码变更
    msgMgr.sendChipsChangeMsg(seat);

    LOG.info("[二人牛牛]玩家[{}][{}]用筹码[{}]兑换金币[{}]成功", player.getPlayerName(), player.getId(), chips,
        gold);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家退出房间 .
   * 
   * @param player 玩家.
   */
  public void exitRoom(Player player) {
    AbstractRoom curRoom = player.curRoom;
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());

    if (curRoom instanceof ErRenNiuNiuRoom) {
      if (seat == null) {
        player.curRoom = null;
        ErRenNiuNiuRoom room = (ErRenNiuNiuRoom) curRoom;
        room.getPlayers().remove(player.getId());
        msgMgr.sendExitRoomMsg(player, 0);

        LOG.info("[二人牛牛]玩家[{}][{}]退出房间[{}]", player.getPlayerName(), player.getId(), room.getId());
        // 触发退出房间事件
        eventMgr.post(new ExitRoomEvent(player, curRoom));
      } else {
        LOG.error("[二人牛牛]玩家[{}][{}]在座位中不能退出房间,请先退出桌子", player.getPlayerName(), player.getId());
        msgMgr.sendExitRoomMsg(player, 1);
      }
    } else {
      LOG.warn("[二人牛牛]玩家[{}][{}]不在房间中，无法退出房间", player.getPlayerName(), player.getId());
    }
  }

  /**
   * 玩家进入牌桌 .
   * 
   * @param player 玩家.
   * @param tableId .
   * @param order .
   */
  public void enterTable(Player player, int tableId, byte order, String tablePwd) {
    if (!(player.curRoom instanceof ErRenNiuNiuRoom)) {
      LOG.error("[二人牛牛]玩家[{}][{}]当前不在房间中不能进入桌子", player.getPlayerName(), player.getId());
      return;
    }

    ErRenNiuNiuTable table = dataMgr.getTable(tableId);

    if (table == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]进入的牌桌[{}]不存在", player.getPlayerName(), player.getId(), tableId);
      return;
    }

    if (dataMgr.getPlayerSeat(player.getId()) != null) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    ErRenNiuNiuSeat seat = table.getSeat(order);
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]进入的牌桌位置[{}]不合法", player.getPlayerName(), player.getId(), order);
      return;
    }

    if (seat.getPlayerId() > 0) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_SET_NOT_EMPTY, Operator.SYSTEM,
          seat.getPlayerName());
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的牌桌位置[{}]已经有人", player.getPlayerName(), player.getId(), order);
      return;
    }

    // 进入座位
    doEnterTable(player, seat, tablePwd);
  }

  /**
   * 快速进入二人牛牛 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void fastEnterTable(Player player, int roomId) {
    if (!(player.curRoom instanceof ErRenNiuNiuRoom)) {
      LOG.error("[二人牛牛]玩家[{}][{}]当前不在房间中不能直接进入桌子", player.getPlayerName(), player.getId());
      return;
    }

    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    ErRenNiuNiuRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]快速进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    ErRenNiuNiuSeat emptySeat = findEmptySeat(player, room);
    if (emptySeat != null) {
      doEnterTable(player, emptySeat, null);
    } else {
      // 发送快速进入桌子结果
      msgMgr.sendFastEnterTableMsg(player, 1);
      commonMsgMgr.sendTipDlgMsg(player, ErrorCode.GAME_NOT_SIT, Operator.SYSTEM);
    }
  }

  /**
   * 玩家进入座位 .
   * 
   * @param player 玩家.
   * @param seat 座位信息.
   */
  private void doEnterTable(Player player, ErRenNiuNiuSeat seat, String tablePwd) {
    int roomId = seat.table.room.getId().intValue();
    ErrenniuniuRoomDomain roomDom = roomDic.get(seat.table.room.getId().intValue());
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getLower());
      return;
    }

    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getUpper());
      return;
    }

    // 玩家进桌前的房间设置校验
    if (!gameHallMgr.checkTableSetting(player, seat.table(), tablePwd)) {
      return;
    }

    ErRenNiuNiuTable table = seat.table;
    if (player.getGold() < roomDom.getAfee()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_ENOUGH_FEE, Operator.SYSTEM);
      LOG.warn("[二人牛牛]玩家[{}][{}]进入桌子金币不足台费", player.getPlayerName(), player.getId());
      return;
    } else {
      playerMgr.minusGold(player, roomDom.getAfee(), LogReason.ERREN_NIUNIU_ENTER_TABLE_MINUS_AFEE);
      DbLogService.log(new GameAfeeLog(player, table, roomDom.getAfee()));
    }

    seat.setPlayerId(player.getId());
    seat.setPlayerName(player.getPlayerName());
    seat.setSex(player.getSex());
    seat.setRobot(player.robot());
    seat.setState(SeatState.SEATED);

    // 更新玩家座位
    dataMgr.updatePlayerSeats(player.getId(), seat);
    // 触发进入桌子事件
    eventMgr.post(new EnterTableEvent(player, seat));

    // 向玩家发送进入牌桌结果信息
    msgMgr.sendEnterTableMsg(player, table);
    msgMgr.sendOtherEnterTableMsg(seat);
    LOG.info("[二人牛牛]玩家[{}][{}]进入桌子[{}]座位[{}]成功", player.getPlayerName(), player.getId(),
        table.getId(), seat.getOrder());

    int readyTime = timeDic.map().get(1).getTime();
    // 进入座位后如果没有在指定时间内准备好，则退出座子
    seat.stepFuture = scheduleMgr.schedule(() -> doExitTabl(seat), readyTime + ADDED_DELAY,
        TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 玩家退出牌桌 .
   * 
   * @param player 玩家.
   */
  public void exitTable(Player player) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]没在牌桌中不能退出牌桌", player.getPlayerName(), player.getId());
      return;
    }

    // 退出桌子
    doExitTabl(seat);
  }

  /**
   * 退出桌子 .
   * <p/>
   * 1.玩家正常退出牌桌,能在在线玩家中拿到player,正常退出桌子
   * <p/>
   * 2.玩家非正常退出(如掉线)后, 从活跃玩家或数据库中拿到PlayerDomain，结算后马上更新缓冲和数据库
   * 
   * @param seat 座位信息.
   */
  private void doExitTabl(ErRenNiuNiuSeat seat) {


    ErRenNiuNiuSeat otherSeat = seat.otherSeat();

    if (seat.cards.size() > 0) { // 已经发牌,没有摊牌则摊牌?
      // doShowdown会重置seat数据
      boolean otherSeatShowdown = otherSeat.showdown;
      if (!seat.showdown) {
        doShowdown(seat);
      }

      if (!otherSeatShowdown) {
        doShowdown(otherSeat);
      }
    } else if (seat.readied() && otherSeat.readied()) { // 都准备过(游戏状态),另一个seat恢复到初始态
      otherSeat.resetButTotalChips();
      seat.table.reset();
      // 游戏结束后没有在指定时间内准备好，则退出座子
      int readyTime = timeDic.map().get(1).getTime();
      otherSeat.stepFuture = scheduleMgr.schedule(() -> doExitTabl(otherSeat),
          readyTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
      eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(otherSeat.getPlayerId())));
    }


    // 发送退出房间消息
    msgMgr.sendExitTableMsg(seat);
    dataMgr.removePlayerSeat(seat.getPlayerId());
    LOG.info("[二人牛牛]玩家[{}][{}]退出桌子[{}]", seat.getPlayerId(), seat.getPlayerName(),
        seat.table.getId());
    seat.clear();
    long playerId = seat.getPlayerId();
    Player player = playerMgr.getPlayer(playerId);
    // 触发退出桌子事件
    eventMgr.post(new ExitTableEvent(player, seat));
  }

  /**
   * 筹码换算金币,只会是多个金币等于一个筹码 .
   * 
   * @param roomId .
   * @param chips .
   */
  public long convertGold(int roomId, long chips) {
    ErrenniuniuRoomDomain roomDomain = roomDic.map().get(roomId);

    return chips * roomDomain.getProportionGold() / roomDomain.getProportionChips();
  }

  /**
   * 金币换算筹码 .
   * 
   * @param roomId .
   * @param gold .
   * @return
   */
  public long convertChip(int roomId, long gold) {
    ErrenniuniuRoomDomain roomDomain = roomDic.map().get(roomId);

    return gold * roomDomain.getProportionChips() / roomDomain.getProportionGold();
  }

  /**
   * 玩家准备 .
   * 
   * @param player 玩家.
   */
  public void ready(Player player) {
    // 座位
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]还未进入牌桌不能准备", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.readied()) {
      LOG.warn("[二人牛牛]玩家[{}][{}]已经准备好,请勿重复准备", player.getPlayerName(), player.getId());
      msgMgr.sendReadyMsg(seat, 1);
      return;
    }

    ErRenNiuNiuTable table = seat.table;
    int roomId = table.room.getId().intValue();
    ErrenniuniuRoomDomain roomDomain = roomDic.map().get(roomId);

    // 校验玩家身上的钱换成的筹码是否足够(最小筹码*8)
    if (seat.getTotalChips() < roomDomain.getMinOne() * 8) {
      LOG.warn("[二人牛牛]玩家[{}][{}]玩家身上的筹码不足[{}(最小筹码*8)]不能准备", player.getPlayerName(), player.getId(),
          roomDomain.getMinOne() * 8);
      return;
    }

    ErRenNiuNiuSeat otherSeat = seat.otherSeat();
    seat.setState(SeatState.READY);
    // 清除准备计时
    seat.stepFuture.cancel(false);

    if (otherSeat.readied()) { // 都准备完成,对下一个叫庄的玩家定时
      // 随机叫庄seat
      ErRenNiuNiuSeat callSeat = RandCodeUtil.randomBoolean() ? seat : otherSeat;
      table.nextCallPlayer = callSeat.getPlayerId();

      seat.setState(SeatState.GAMING);
      otherSeat.setState(SeatState.GAMING);

      // 清除准备计时
      callSeat.stepFuture.cancel(false);
      callSeat.otherSeat().stepFuture.cancel(false);
      // 触发房间大厅成员信息变更事件
      eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(otherSeat.getPlayerId())));

      // 都准备完成后对叫庄玩家schedule
      int callTime = timeDic.get(2).getTime();
      callSeat.stepFuture = scheduleMgr.schedule(() -> doCallDealer(callSeat, false),
          callTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    }

    msgMgr.sendReadyMsg(seat, 0);
    LOG.info("[二人牛牛]玩家[{}][{}]准备好", player.getPlayerName(), player.getId());

    // 向同桌的玩家发送该座位的玩家已经准备好
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家换桌 .
   * 
   * @param player 玩家.
   */
  public void exchangeTable(Player player) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]没在座位中不能换位置", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.readied()) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经准备好不能换桌子", player.getPlayerName(), player.getId());
      msgMgr.sendExchangeTableMsg(player, 1);
      return;
    }

    ErRenNiuNiuRoom room = seat.table.room;

    // 交换到其他桌的位置
    ErRenNiuNiuSeat exchangedSeat = findEmptySeat(player, room);
    if (exchangedSeat == null) {
      LOG.warn("[二人牛牛]当前房间没有空位置给玩家[{}][{}]交换", player.getPlayerName(), player.getId());
      msgMgr.sendExchangeTableMsg(player, 2);
    } else {
      // 退出桌子
      exitTable(player);
      // 进入位置
      doEnterTable(player, exchangedSeat, null);
    }
  }

  /**
   * 查找空位置,优先查找有人的桌子 .
   * 
   * @param player 玩家.
   * @param room .
   * @return
   */
  private ErRenNiuNiuSeat findEmptySeat(Player player, ErRenNiuNiuRoom room) {
    ErRenNiuNiuSeat emptySeat = null;
    ArrayList<ErRenNiuNiuTable> tables = new ArrayList<>(room.tables.values());
    Collections.shuffle(tables);

    for (ErRenNiuNiuTable table : tables) {
      if (table.getPassword() != null) {
        continue;
      }

      if (table.getLimitIp() != null && table.getLimitIp().equals(player.ip)) {
        continue;
      }

      if (player.getGold() < table.getLimitGold()) {
        continue;
      }

      boolean hasPlayer = false;
      ErRenNiuNiuSeat tableEmptySeat = null;
      List<ErRenNiuNiuSeat> seats = Lists.newArrayList(table.seat1, table.seat2);

      for (ErRenNiuNiuSeat seat : seats) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
          tableEmptySeat = seat;
        } else {
          hasPlayer = true;
        }
      }

      if (hasPlayer && tableEmptySeat != null) {
        return tableEmptySeat;
      }
    }

    return emptySeat;
  }

  /**
   * 玩家叫庄家 .
   * 
   * @param player 玩家.
   * @param call . 是否叫庄 .
   */
  public void callDealer(Player player, boolean call) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]不在牌桌中不能叫庄", player.getPlayerName(), player.getId());
      return;
    }

    long nextCallPlayer = seat.table.nextCallPlayer;
    ErRenNiuNiuSeat nextCallSeat = dataMgr.getPlayerSeat(nextCallPlayer);
    if (nextCallSeat != seat) {
      LOG.error("[二人牛牛]当前叫庄玩家为[{}],玩家[{}][{}]不能叫庄", nextCallPlayer, player.getPlayerName(),
          player.getId());
      return;
    }

    if (seat.cards.size() > 0) {
      LOG.error("[二人牛牛]玩家[{}][{}]正在游戏中不能叫庄", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.called) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经叫过庄不能不能重复叫庄", player.getPlayerName(), player.getId());
      return;
    }

    // 叫庄
    doCallDealer(seat, call);
  }

  /**
   * 叫庄 .
   * 
   * @param seat 座位信息.
   * @param call . true:叫,false:不叫 .
   */
  private void doCallDealer(ErRenNiuNiuSeat seat, boolean call) {
    ErRenNiuNiuTable table = seat.table;
    ErRenNiuNiuSeat otherSeat = seat.otherSeat();
    seat.called = true;
    seat.dealer = call;
    seat.stepFuture.cancel(false);

    if (call) { // 如果叫庄则不用继续叫庄
      otherSeat.dealer = false;
      otherSeat.called = true;
    } else {
      if (otherSeat.called) { // 两个都不叫庄家随机指派一个玩家做庄
        if (RandCodeUtil.randomBoolean()) {
          seat.dealer = true;
        } else {
          otherSeat.dealer = true;
        }
      } else {
        table.nextCallPlayer = otherSeat.getPlayerId();
        int callTime = timeDic.get(2).getTime();
        otherSeat.stepFuture = scheduleMgr.schedule(() -> doCallDealer(otherSeat, false),
            callTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
      }
    }

    ErRenNiuNiuSeat dealer = table.dealer();
    if (dealer != null) {
      // 确定庄家后闲家下注
      int betTime = timeDic.get(3).getTime();
      // 闲家
      ErRenNiuNiuSeat playerSeat = dealer.otherSeat();
      playerSeat.stepFuture =
          scheduleMgr.schedule(() -> doBet(playerSeat, abledBetChips(table).get(0)),
              betTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    }

    LOG.info("[二人牛牛]玩家[{}][{}][{}]", seat.getPlayerName(), seat.getPlayerId(), call ? "叫庄" : "不叫");
    // 发送叫庄消息
    msgMgr.sendCallDealerMsg(seat, call ? 1 : 0);
  }

  /**
   * 玩家下注 .
   * 
   * @param player 玩家.
   * @param chips .
   */
  public void bet(Player player, long chips) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]不在牌桌中不能下注", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.cards.size() > 0) {
      LOG.error("[二人牛牛]玩家[{}][{}]正在玩牌不能下注", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.dealer) {
      LOG.error("[二人牛牛]玩家[{}][{}]是庄家不能下注", player.getPlayerName(), player.getId());
      return;
    }

    ErRenNiuNiuTable table = seat.table;
    if (table.dealer() == null) {
      LOG.error("[二人牛牛]还未确定庄家,玩家[{}][{}]不能下注", player.getPlayerName(), player.getId());
      return;
    }

    List<Long> abledBetChips = abledBetChips(seat.table);
    if (!abledBetChips.contains(chips)) {
      msgMgr.sendBetMsg(seat, 1);
      LOG.error("[二人牛牛]玩家[{}][{}]下注[{}]不在列表{}中", player.getPlayerName(), player.getId(), chips,
          abledBetChips);
      return;
    }

    // 下注
    doBet(seat, chips);
  }

  /**
   * 下注(闲家才能下注) .
   * 
   * @param seat 座位信息.
   * @param chips .
   */
  private void doBet(ErRenNiuNiuSeat seat, long chips) {
    seat.stepFuture.cancel(false);
    seat.betedchips = chips;
    // 发送下注消息
    msgMgr.sendBetMsg(seat, 0);

    ErRenNiuNiuTable table = seat.table;
    LOG.info("[二人牛牛]闲家[{}][{}]下注[{}]结束,开始洗牌发牌", seat.getPlayerName(), seat.getPlayerId(), chips);
    Collections.shuffle(table.cards);

    // 控制牛牛发牌
    niuniuControl.controlDealCards(table);
  }

  /**
   * 发牌 .
   * 
   * @param table .
   * @param seatCards .
   * @param otherSeatCards .
   */
  void doDealCards(ErRenNiuNiuTable table, ErRenNiuNiuSeat seat, List<NiuNiuCard> seatCards,
      ErRenNiuNiuSeat otherSeat, List<NiuNiuCard> otherSeatCards) {
    seat.cards.clear();
    otherSeat.cards.clear();
    seat.cards.addAll(seatCards);
    otherSeat.cards.addAll(otherSeatCards);

    // 闲家下注的筹码
    long betedchips = table.dealer().otherSeat().betedchips;

    // seat1提示的最优牌
    NiuNiuCardsType seatCardsType = NiuNiuCardsTypeGetter.getCardsType(seatCards, seat.bestCards);
    // seat2提示的最优牌
    NiuNiuCardsType otherSeatCardsType =
        NiuNiuCardsTypeGetter.getCardsType(otherSeat.cards, otherSeat.bestCards);

    seat.cardsType = seatCardsType;
    otherSeat.cardsType = otherSeatCardsType;

    boolean win = NiuNiuCardsTypeComparator.compare(seat.cards, seatCardsType, otherSeat.cards,
        otherSeatCardsType);

    if (win) { // seat赢
      seat.billChips = seatCardsType.multiple * betedchips;
      otherSeat.billChips = -seatCardsType.multiple * betedchips;
    } else { // otherSeat赢
      otherSeat.billChips = otherSeatCardsType.multiple * betedchips;
      seat.billChips = -otherSeatCardsType.multiple * betedchips;
    }

    // 向同桌的玩家发送牌消息
    msgMgr.sendDealCardsMsg(seat);
    msgMgr.sendDealCardsMsg(otherSeat);

    // 发牌后schedule摊牌
    int showdownTime = timeDic.get(4).getTime();
    seat.stepFuture = scheduleMgr.schedule(() -> doShowdown(seat), showdownTime + ADDED_DELAY,
        TimeUnit.SECONDS, gameExecutor);
    otherSeat.stepFuture = scheduleMgr.schedule(() -> doShowdown(otherSeat),
        showdownTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);

    LOG.info("[二人牛牛]seat[{}][{}]牌[{}]结算[{}],seat[{}][{}]牌[{}]结算[{}]", seat.getPlayerId(),
        seat.getPlayerName(), seat.bestCards, seat.billChips, otherSeat.getPlayerId(),
        otherSeat.getPlayerName(), otherSeat.bestCards, otherSeat.billChips);
  }

  /**
   * 玩家摊牌 .
   * 
   * @param player 玩家.
   * @param playerCardIds . 玩家选择的3张牌的组合,如果玩家自己选择的牌是最优的牌则用玩家的 .
   */
  public void showdown(Player player, List<Integer> playerCardIds) {
    ErRenNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[二人牛牛]玩家[{}][{}]不在牌桌中不能摊牌", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.cards.size() == 0) {
      LOG.error("[二人牛牛]牌局还未开始，玩家[{}][{}]不能摊牌", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.showdown) {
      LOG.error("[二人牛牛]玩家[{}][{}]已经摊过牌不能摊牌", player.getPlayerName(), player.getId());
      return;
    }

    /*
     * 计算玩家自己选择的牌是不是最优的牌 .
     */
    // 玩家选择的3张数字和
    int totalNum = 0;
    // 玩家自定义的最优牌
    List<NiuNiuCard> playerBestCards = new ArrayList<>();
    for (int i = 0; i < playerCardIds.size() && i < 3; i++) {
      NiuNiuCard card = NiuNiuCard.card(playerCardIds.get(i));
      playerBestCards.add(card);
      if (seat.cards.contains(card)) {
        totalNum += card.num;
      } else {
        break;
      }

      // 有大王或者小王totalNum肯定是10的倍数
      if (i == 2 && (totalNum % 10 == 0 || playerBestCards.contains(NiuNiuCard.DA_WANG)
          || playerBestCards.contains(NiuNiuCard.XIAO_WANG))) {
        List<NiuNiuCard> seatCards = new ArrayList<>(seat.cards);
        seatCards.removeAll(playerBestCards);
        // 玩家自定义牌组合的牛类型
        int niu = (seatCards.get(0).num + seatCards.get(1).num) % 10;
        NiuNiuCardsType playerBestCardsType =
            (niu == 0 ? NiuNiuCardsType.geCardsType(10) : NiuNiuCardsType.geCardsType(niu));
        if (seat.cardsType == playerBestCardsType) {
          seat.bestCards.clear();
          seat.bestCards.addAll(playerBestCards);
          seat.bestCards.addAll(seatCards);
        }
      }
    }

    // 摊牌
    doShowdown(seat);
  }

  /**
   * 摊牌 .
   * 
   * @param seat 座位信息.
   */
  private void doShowdown(ErRenNiuNiuSeat seat) {
    seat.stepFuture.cancel(false);
    seat.showdown = true;

    if (seat.cardsType == null) {
      return;
    }

    msgMgr.sendShowdownMsg(seat);

    ErRenNiuNiuTable table = seat.table;

    LOG.info("[二人牛牛]玩家[{}][{}]摊牌", seat.getPlayerName(), seat.getPlayerId());
    // 2个玩家都摊牌则游戏结束，并且结算
    if (table.seat1.showdown && table.seat2.showdown) {
      gameOver(table);
    }
  }

  /**
   * 该牌桌游戏结束 .
   * 
   * @param table .
   */
  public void gameOver(ErRenNiuNiuTable table) {
    ErRenNiuNiuSeat seat1 = table.seat1;
    ErRenNiuNiuSeat seat2 = table.seat2;


    // 扣除输赢
    seat1.setTotalChips(seat1.getTotalChips() + seat1.billChips);
    seat2.setTotalChips(seat2.getTotalChips() + seat2.billChips);
    table.reset();
    int roomId = seat1.table.room.getId().intValue();

    long player1Id = seat1.getPlayerId();
    long player2Id = seat2.getPlayerId();
    /*
     * 游戏结算金币 .
     */
    Player player1 = playerMgr.getPlayer(player1Id);
    Player player2 = playerMgr.getPlayer(player2Id);
    PlayerBo player1Dom = playerMgr.selectPlayer(player1Id);
    PlayerBo player2Dom = playerMgr.selectPlayer(player2Id);
    long player1BillGold = convertGold(roomId, seat1.billChips);
    long player2BillGold = convertGold(roomId, seat2.billChips);

    if (player1 != null) {
      playerMgr.addGold(player1, player1BillGold, LogReason.ERREN_NIUNIU_BILL);
    } else {
      playerMgr.addGold(player1Dom, player1BillGold, LogReason.ERREN_NIUNIU_BILL);
    }

    if (player2 != null) {
      playerMgr.addGold(player2, player2BillGold, LogReason.ERREN_NIUNIU_BILL);
    } else {
      playerMgr.addGold(player2Dom, player2BillGold, LogReason.ERREN_NIUNIU_BILL);
    }

    // 数据库输赢日志
    DbLogService.log(new Game2WinLoseLog(player1Dom, seat1, seat1.billChips, player1BillGold,
        LogReason.ERREN_NIUNIU_BILL));
    DbLogService.log(new Game2WinLoseLog(player2Dom, seat2, seat2.billChips, player2BillGold,
        LogReason.ERREN_NIUNIU_BILL));

    // 如果满足条件发送牛牛公告
    sendedNotice(seat1.getPlayerName(), seat1.cardsType.niu, seat1.billChips);
    sendedNotice(seat2.getPlayerName(), seat2.cardsType.niu, seat2.billChips);

    seat1.resetButTotalChips();
    seat2.resetButTotalChips();

    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat1);
    msgMgr.sendChipsChangeMsg(seat2);

    msgMgr.sendGameOver(table);
    int readyTime = timeDic.map().get(1).getTime();
    // 游戏结束后没有在指定时间内准备好，则退出座子
    seat1.stepFuture = scheduleMgr.schedule(() -> doExitTabl(seat1), readyTime + ADDED_DELAY,
        TimeUnit.SECONDS, gameExecutor);
    // 游戏结束后没有在指定时间内准备好，则退出座子
    seat2.stepFuture = scheduleMgr.schedule(() -> doExitTabl(seat2), readyTime + ADDED_DELAY,
        TimeUnit.SECONDS, gameExecutor);

    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(player1Id)));
    eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(player2Id)));
  }

  /**
   * 如果满足条件发送牛牛公告 .
   * 
   * @param playerName .
   * @param cardsType . 牌型 .
   * @param chips . 赢得筹码 .
   */
  private void sendedNotice(String playerName, int cardsType, long chips) {

    for (GameNoticeDomainWrapper dom : gameNoticeDic.getGameDoms()
        .get(Game.ERREN_NIUNIU.getType())) {
      JSONObject conditionData = dom.conditionData();
      // 公告条件牌型
      Integer conditionCardsType = conditionData.getInteger("cardsType");
      // 公告条件赢得最小筹码
      Integer conditionMinChips = conditionData.getInteger("minChips");

      if (cardsType == conditionCardsType && chips >= conditionMinChips) {
        String sendContent = MessageFormat.format(dom.getContent(), playerName, chips);
        if (dom.getMarqueeShow() == BoolInteger.TRUE) { // 跑马灯公告
          noticeMsgMgr.sendMarqueeNoticeMsg(sendContent, NoticeType.GAME, dom.getInterval(),
              dom.getTimes(), dom.getColor());
        }

        if (dom.getChatShow() == BoolInteger.TRUE) { // 聊天公告
          chatMsgMgr.sendSysNoticeMsg(sendContent);
        }
      }
    }
  }

  /**
   * 该seat可用的下注的集合,用庄家和闲家中最小的一家来计算 .
   * 
   * @param seats .
   * @return
   */
  private List<Long> abledBetChips(ErRenNiuNiuTable table) {
    ErRenNiuNiuRoom room = table.room;
    ErrenniuniuRoomDomain roomDomain = roomDic.map().get(room.getId().intValue());
    ErRenNiuNiuSeat seat1 = table.seat1;
    ErRenNiuNiuSeat seat2 = table.seat2;
    long minChips = seat1.getTotalChips() > seat2.getTotalChips() ? seat2.getTotalChips()
        : seat1.getTotalChips();

    // 最低下注
    long minOne = roomDomain.getMinOne();
    long maxOne = roomDomain.getMaxOne();

    if (minChips < minOne * 2 * 8) {
      return Arrays.asList(minOne);
    } else if (minChips < minOne * 3 * 8) {
      return Arrays.asList(minOne, minOne * 2);
    } else if (minChips < minOne * 4 * 8) {
      return Arrays.asList(minOne, minOne * 2, minOne * 3);
    } else if (minChips < maxOne * 4 * 8) {
      return Arrays.asList(minChips / 32, minChips / 16, minChips * 3 / 32, minChips / 8);
    } else {
      return Arrays.asList(maxOne, maxOne * 2, maxOne * 3, maxOne * 4);
    }
  }

  /**
   * 清除玩家,服务器出现bug，玩家卡在游戏中，后台可以清除玩家 .
   * 
   * @param playerId 玩家id.
   */
  private void doClearGameData(long playerId) {
    try {

      dataMgr.removePlayerSeat(playerId);
      /*
       * 清除玩家房间数据 .
       */
      for (ErRenNiuNiuRoom room : dataMgr.rooms()) {
        room.getPlayers().remove(playerId);

        /*
         * 清除房间中玩家所在桌子数据 .
         */
        for (ErRenNiuNiuTable table : room.tables.values()) {
          long player1Id = table.seat1.getPlayerId();
          long player2Id = table.seat2.getPlayerId();

          if (player1Id == playerId || player2Id == playerId) {
            table.reset();
            dataMgr.removePlayerSeat(player2Id);
            dataMgr.removePlayerSeat(player1Id);
            room.getPlayers().remove(player2Id);
            room.getPlayers().remove(player1Id);

            /*
             * 清除座位数据 .
             */
            table.seat1.clear();
            table.seat2.clear();
            Player player = playerMgr.getPlayer(player1Id);
            Player otherPlayer = playerMgr.getPlayer(player2Id);
            if (player != null) {
              loginMgr.noticeLogoutForGameExit(player);
            }

            if (otherPlayer != null) {
              loginMgr.noticeLogoutForGameExit(otherPlayer);
            }
          }
        }
      }

      Player player = playerMgr.getPlayer(playerId);
      if (player != null) {
        loginMgr.noticeLogoutForGameExit(player);
      }
    } catch (Exception e) {
      LOG.error("[二人牛牛]清除玩家[" + playerId + "]卡限失败", e);
    }
  }
}
